﻿//The Tile class represents tiles that appear on a TileBasedSpriteSurface

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Xml.Serialization;

namespace SCG.TurboSprite.TileBased
{
    [Serializable]
    public class Tile
    {
        //constructors
        public Tile()
        {
        }
        public Tile(string name, TileType type, TileBasedSpriteSurface surface)
        {
            _name = name;
            _tileType = type;
            _surface = surface;
        }
        public Tile(Tile tile)
        {
            _adjacent = tile._adjacent;
            _bounds = tile._bounds;
            _center = tile._center;
            _color = tile._color;
            _foreColor = tile._foreColor;
            _name = tile._name;
            _pointArray = tile._pointArray;
            _points = tile._points;
            _segments = tile._segments;
            _surface = tile._surface;
            _tileType = tile._tileType;
            _titleLoc = tile._titleLoc;
            _index = tile._index;
        }

        //Access the surface
        [XmlIgnore]
        public TileBasedSpriteSurface Surface
        {
            get
            {
                return _surface;
            }
            set
            {
                _surface = value;
            }
        }

        //Access the points that make up the tile
        public List<Point> Points
        {
            get
            {
                return _points;
            }
        }

        //Name of the zone
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
            }
        }

        //Lane or sea?
        public TileType TileType
        {
            get
            {
                return _tileType;
            }
            set
            {
                _tileType = value;
            }
        }

        //Title location
        public Point TitleLocation
        {
            get
            {
                return _titleLoc;
            }
            set
            {
                _titleLoc = value;
            }
        }

        //Color
        public virtual Color Color
        {
            get
            {
                if (_color == Color.Empty)
                    return TileType == TileType.Land ? _surface.ColorLandTiles : _surface.ColorSeaTiles;
                else
                    return _color;
            }
            set
            {
                _color = value;
            }
        }

        //Font color
        public virtual Color ForeColor
        {
            get
            {
                return TileType == TileType.Land ? Color.Black : Color.Aqua;
            }
            set
            {
                _foreColor = value;
            }
        }

        //The tile's internal index into the list
        [XmlIgnore]
        public int Index
        {
            get
            {
                return _index;
            }
        }

        //Access the center point of the tile
        [XmlIgnore]
        public Point Center
        {
            get
            {
                if (_bounds.Left == -1)
                    CalculateBounds();
                return _center;
            }
            internal set
            {
                _center = value;
            }
        }

        //Bounding rectangle
        [XmlIgnore]
        public Rectangle Bounds
        {
            get
            {
                if (_bounds.Left == -1)
                    CalculateBounds();
                return _bounds;
            }
        }

        //Access the adjacent tiles, create on demand, populated by MapSet
        [XmlIgnore]
        public List<Tile> Adjacent
        {
            get
            {
                return _adjacent;
            }
        }

        //Is it a shore?
        [XmlIgnore]
        public bool IsShore
        {
            get
            {
                if (TileType == TileType.Sea)
                    return false;
                else
                {
                    foreach (Tile adj in Adjacent)
                        if (adj.TileType == TileType.Sea)
                            return true;
                    return false;
                }
            }
        }

        //Check adjacency       
        public bool IsAdjacent(Tile tile)
        {
            return Adjacent.Contains(tile);
        }

        //Access line segments, populate on demand
        [XmlIgnore]
        public IList<Segment> Segments
        {
            get
            {
                if (_segments.Count == 0)
                {               
                    for (int i = 0; i < Points.Count - 1; i++)                                      
                        _segments.Add(new Segment(Points[i].X, Points[i].Y, Points[i + 1].X, Points[i + 1].Y));                   

                    //create the last segment linking back to the first
                    Segment last = new Segment();
                    int idx = Points.Count - 1;
                    last.X1 = Points[idx].X;
                    last.Y1 = Points[idx].Y;
                    last.X2 = Points[0].X;
                    last.Y2 = Points[0].Y;
                    _segments.Add(last);
                }
                return _segments;
            }            
        }

        //Cost to traverse zone to zone (assuming neighbors)
        //Non-traversable should return Double.MaxValue
        public virtual double CostToTraverse(Tile tile)
        {
            if (TileType != tile.TileType)
                return Double.MaxValue;
            return 1;
        }

        //Indicates that the tile should vibrate when being rendered
        [XmlIgnore]
        public bool Vibrating { get; set; }

        //String representation
        public override string ToString()
        {
            return Name;
        }

        //private members
        private static Pen TILE_PEN = new Pen(Color.Black);
        private static SolidBrush _brush = new SolidBrush(Color.Black);     
        private List<Point> _points = new List<Point>();
        internal Point[] _pointArray = null;
        private Point _center = new Point();
        private Rectangle _bounds = new Rectangle(-1, -1, 0, 0);
        private string _name;
        private TileType _tileType = TileType.Land;
        private Point _titleLoc = new Point(-1, -1);
        private Color _color = Color.Empty;
        private List<Tile> _adjacent = new List<Tile>();
        private List<Segment> _segments = new List<Segment>();
        private TileBasedSpriteSurface _surface;
        private Color _foreColor;
        internal int _index;
        private Point _vibrateOffset = new Point(0, 0);
        private Random _rng = new Random();
    
        //A Path currently being explored
        internal Path PathExplored { get; set; }

        //Render a shorepath
        protected internal virtual void RenderShorePath(Graphics g, List<Segment> path)
        {
            //Offset into a point array
            Point[] pts = new Point[path.Count + 1];
            for (int p = 0; p < path.Count; p++)
                pts[p] = new Point(path[p].X1 - Surface.OffsetX, path[p].Y1 - Surface.OffsetY);
            int last = path.Count - 1;
            pts[last + 1] = new Point(path[last].X2 - Surface.OffsetX, path[last].Y2 - Surface.OffsetY);
            GraphicsPath gp = new GraphicsPath();
            gp.AddLines(pts);

            //Render the shoreline
            int n = _surface.ShoreCount;
            int penWidth = _surface.ShoreWidth * n;
            Color baseColor = _surface.ColorSeaTiles;
            while (n > 0)
            {
                //obtain a lighter version of the previous base color
                byte R = baseColor.R;
                byte G = baseColor.G;
                byte B = baseColor.B;
                byte A = baseColor.A;
                float mult = ((float)_surface.ShoreFade / 100.0f) + 1.0f;
                R = (byte)(R * mult + _surface.ShoreFade);
                G = (byte)(G * mult + _surface.ShoreFade);
                B = (byte)(B * mult + _surface.ShoreFade);
                Color newColor = Color.FromArgb(A, R, G, B);
                TILE_PEN.Color = newColor;
                TILE_PEN.Width = penWidth;
                g.DrawPath(TILE_PEN, gp);
                n--;
                penWidth -= _surface.ShoreWidth;
                baseColor = newColor;
            }
        }

        //Fill the background
        protected internal virtual void FillBackground(Graphics g)
        {
            //Ensure point array matches list
            if (_bounds.Left == -1 || _pointArray == null)
                CalculateBounds();

            //offset it
            for (int p = 0; p < _points.Count; p++)
            {
                _pointArray[p].X = _points[p].X - _surface.OffsetX;
                _pointArray[p].Y = _points[p].Y - _surface.OffsetY;
            }

            //Fill with its color
            _brush.Color = Color;
            g.FillPolygon(_brush, _pointArray);
        }

        //Render the tile
        protected internal virtual void Render(Graphics g)
        {
            Point[] points = (Point[])_pointArray.Clone();

            //Vibration
            if (Vibrating)
            {
                _vibrateOffset.X = _rng.Next(3) - 1;
                _vibrateOffset.Y = _rng.Next(3) - 1;
                for (int p = 0; p < _pointArray.Length; p++)
                {
                    Point pt = points[p];
                    pt.Offset(_vibrateOffset);
                    points[p] = pt;
                }
            }
            else
            {
                _vibrateOffset.X = 0;
                _vibrateOffset.Y = 0;
            }

            //Render it, clip to polygon for outlined effects
            GraphicsPath gp = new GraphicsPath();
            gp.AddPolygon(points);
            Region rgn = new Region(gp);
            g.Clip = rgn;

            //Execute outlining effects, wider to narrower
            if (TileType == TileType.Land)
            {
                int n = _surface.OutlineCountLand;
                int penWidth = _surface.OutlineWidthLand * n;
                Color baseColor = Color;
                while (n > 0)
                {
                    //obtain a darker version of the previous base color
                    byte R = baseColor.R;
                    byte G = baseColor.G;
                    byte B = baseColor.B;
                    byte A = baseColor.A;
                    float mult = (100.0f - _surface.OutlineFadeLand) / 100.0f;
                    R = (byte)(R * mult);
                    G = (byte)(G * mult);
                    B = (byte)(B * mult);
                    Color newColor = Color.FromArgb(A, R, G, B);
                    TILE_PEN.Color = newColor;
                    TILE_PEN.Width = penWidth;
                    g.DrawPolygon(TILE_PEN, points);
                    n--;
                    penWidth -= _surface.OutlineWidthLand;
                    baseColor = newColor;
                }
            }
            g.ResetClip();

            //outline with border
            if (TileType == TileType.Land)
            {
                TILE_PEN.Color = _surface.BorderColorLand;
                TILE_PEN.Width = _surface.BorderWidthLand;
            }
            else
            {
                TILE_PEN.Color = _surface.BorderColorSea;
                TILE_PEN.Width = _surface.BorderWidthSea;
            }
            g.DrawPolygon(TILE_PEN, points);

            //Draw title
            RenderTitle(g);
        }

        //Render the title, allow derived classes to override
        protected virtual void RenderTitle(Graphics g)
        {
            Point pt = TitleLocation;
            g.DrawString(Name, _surface.Font, new SolidBrush(ForeColor), TitleLocation.X - _surface.OffsetX + _vibrateOffset.X, TitleLocation.Y - _surface.OffsetY + _vibrateOffset.Y);
        }

        //Access the vibration offset for derived classes rendering
        protected Point VibrateOffset
        {
            get
            {
                return _vibrateOffset;
            }
        }

        //Calculate bounds and center
        internal void CalculateBounds()
        {
            //move points to array
            _pointArray = new Point[_points.Count];
            for (int i = 0; i < _points.Count; i++)
                _pointArray[i] = _points[i];

            //calculate center and bounds of tile
            int left = Int32.MaxValue;
            int right = Int32.MinValue;
            int top = Int32.MaxValue;
            int bottom = Int32.MinValue;
            foreach (Point pt in _points)
            {
                if (pt.X < left)
                    left = pt.X;
                if (pt.X > right)
                    right = pt.X;
                if (pt.Y < top)
                    top = pt.Y;
                if (pt.Y > bottom)
                    bottom = pt.Y;
            }
            _center.X = (right - left) / 2 + left;
            _center.Y = (bottom - top) / 2 + top;
            _bounds.X = left;
            _bounds.Y = top;
            _bounds.Width = right - left + 1;
            _bounds.Height = bottom - top + 1;

            //Title location if not set
            if (_titleLoc.X == -1 && _points.Count > 0)
                _titleLoc = _points[0];
        }

        //Flag to recalculate internal values
        internal void Recalculate()
        {
            _pointArray = null;
            _segments.Clear();
        }
    }

    //Path finding
    internal enum PathStatus { WasInList, NowInList, NotInList };

    //type of zone
    public enum TileType { Land, Sea };
}
